<?php

namespace App\Kuafu\Shop\Service\DouyinSelfLibrary;

use App\Kuafu\Base\Service\BaseService;
use App\Kuafu\CouponExt\Channel\CouponExtReturn;
use App\Kuafu\CouponExt\Enum\CouponExtCodeEnum;
use App\Kuafu\CouponExt\Exceptions\CouponExtException;
use App\Kuafu\WaiMai\Enum\WaiMaiDBEnum;
use App\Kuafu\WaiMai\Service\WaiMaiShopService;
use App\Library\FeiShu\SendFs;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\RequestOptions;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;

class DouyinSelfBaseService extends BaseService
{
    const TIME_OUT               = 10;
    const SAND_BOX_CLIENT_KEY    = ''; // 沙箱client_key
    const SAND_BOX_CLIENT_SECRET = ''; // 沙箱client_secret
    const SAND_BOX_TOKEN         = ''; // 沙箱token
    const SAND_BOX_ACCOUNT_ID    = ''; // 沙箱商户Id
    const SAND_BOX_ACCOUNT_NAME  = ''; // 沙箱商户名称
    const ACCOUNT_ID             = '7089001835192649766'; // 商户Id

    // 抖音小程序 code2Session 请求url
    const SESSION_SAND_BOX_URL = 'https://open-sandbox.douyin.com/api/apps/v2/jscode2session'; // 沙盒地址
    const SESSION_PROD_URL     = 'https://developer.toutiao.com/api/apps/v2/jscode2session'; // 线上地址

    private string   $host         = 'https://open.douyin.com/';
    protected string $clientKey    = '';
    protected string $clientSecret = '';
    protected string $clientToken  = '';
    protected string $poiId        = '';

    public function __construct($brandId = 0, $shopId = 0) { parent::__construct($brandId, $shopId); }

    protected function getClientKey(): string
    {
        return 'tt71dc0761eee036a201';
    }

    protected function getClientSecret(): string
    {
        return '4a0824877d209f1a81a7efd310b2d8c7b522baff';
    }

    /**
     * 获取门店抖音团购配置的 POI-ID
     *
     * @return string
     * @throws CouponExtException
     */
    public function getTGPoiId(): string
    {
        return $this->getPoiId(WaiMaiDBEnum::SOURCE_TYPE_DOUYIN);
    }

    /**
     * @throws CouponExtException
     */

    /**
     * 获取对应配置的 POI-ID
     *
     * @param int $sourceType
     * @return string
     * @throws CouponExtException
     */
    private function getPoiId(int $sourceType = WaiMaiDBEnum::SOURCE_TYPE_DOUYIN): string
    {
        // 判断 poiId 是否为空
        if (empty($this->poiId)) {
            $config = WaiMaiShopService::getMappingCache($this->shopId, $sourceType);
            if (empty($config) || empty($config->thirdShopId)) {
                throw new CouponExtException(CouponExtCodeEnum::ERR_DOUYIN_CONFIG_EMPTY);
            }

            $this->poiId = $config->thirdShopId;
        }

        return $this->poiId;
    }

    /**
     * 获取 client Token
     *
     * @return void
     * @throws GuzzleException
     */
    public function getClientToken(bool $forceTry = false)
    {
        $redisKey = 'douyin-self:clientToken:v2:';
        if ($forceTry || !$accessToken = Redis::get($redisKey)) {
            $response = (new Client([
                'timeout' => self::TIME_OUT,
                'headers' => [
                    'Content-Type' => "multipart/form-data",
                ],
            ]))->post($this->host . 'oauth/client_token', [
                'form_params' => [
                    'client_key'    => $this->getClientKey(),
                    'client_secret' => $this->getClientSecret(),
                    'grant_type'    => 'client_credential'
                ]
            ]);

            $result = json_decode($response->getBody()->getContents(), true);

            $accessToken = $result['data']['access_token'] ?? '';

            $logData = [
                'contents'    => $result,
                'url'         => $this->host . '/oauth/client_token',
                'accessToken' => $accessToken
            ];

            if (empty($accessToken)) {
                SendFs::newRobot(SendFs::ROBOT_BUSINESS_WARING)
                      ->setTitleContent('抖音获取 Access Token 失败', json_encode($logData, 256))->send();
            }

            Log::info('DouyinBase getClientToken:', $logData);

            Redis::setEx($redisKey, 3600, $accessToken);
        }
        return $accessToken;
    }

    /**
     * 重试次数
     *
     * @var int
     */
    protected static int $requestTime = 0;

    /**
     * 最大重试次数
     *
     * @var int
     */
    protected static int $maxReTryTime = 2;

    /**
     * 抖音通用请求
     *
     * @param string $path
     * @param array $bizParam
     * @param string $requestType get / post
     * @param bool $isSandbox true-沙箱
     * @return mixed
     * @throws GuzzleException
     */
    protected function doCurl(string $path, array $bizParam, string $requestType = 'get', bool $isSandbox = false)
    {
        $requestUrl = $this->host . $path;
        if ($requestType == 'get') {
            $requestUrl .= '?' . http_build_query($bizParam);
        }

        ++self::$requestTime;
        $headers = [
            'Content-Type' => "application/json",
//            'access-token' => $this->getClientToken(),
        ];
        if ($isSandbox) {
            $headers['access-token']    = self::SAND_BOX_TOKEN;
            $headers['X-Sandbox-Token'] = 1;
        } else {
            $headers['access-token'] = $this->getClientToken();
        }

        Log::info('DouyinV2 Request:',
            ['url' => $requestUrl, 'param' => $bizParam, 'requestType' => $requestType, 'headers' => $headers]);
        $httpClient = new Client([
            'timeout' => self::TIME_OUT,
            'headers' => $headers,
        ]);
        if ($requestType == 'get') {
            $response = $httpClient->get($requestUrl);
        } else {
            $response = $httpClient->post($requestUrl, [RequestOptions::JSON => $bizParam]);
        }

        $result = json_decode($response->getBody()->getContents(), true);

        $logData = [
            'url'           => $requestUrl,
            'param'         => $bizParam,
            'requestType'   => $requestType,
            'requestResult' => $result,
        ];
        Log::info('DouyinV2 Response:', $logData);

        if (empty($result) || ($result['extra']['error_code'] ?? 1) != 0) {

            SendFs::newRobot(SendFs::ROBOT_BUSINESS_WARING)
                  ->setTitleContent('抖音V2 接口请求失败2', json_encode($logData, 256))
                  ->send();

            // 如果是 token 过期 就重新获取 然后重试
            if ($result['extra']['error_code'] == 2190008) {
                $this->getClientToken(true);
                return $this->doCurl($path, $bizParam, $requestType);
            }

            // 判断是否超过最大重试次数 TODO 添加重试机制
            if (self::$requestTime >= self::$maxReTryTime) {
                return $result;
            } else {
                return $result;
            }
        }

        // 成功返回后 清零
        self::$requestTime = 0;
        return $result;
    }

    /**
     * 接口返回值统一处理
     * @param array $response
     * @return array
     */
    protected function commonReturn(array $response): array
    {
        $code = $response['extra']['error_code'] ?? '';
        $msg  = $response['extra']['description'] ?? '';
        $data = $response['data'] ?? [];

        $status = !($code !== 0);
        return self::returnFormatData($status, $code, $msg, $data);
    }

    /**
     * 接口返回通用处理
     * @param bool $status
     * @param string $code
     * @param string $msg
     * @param array $oriData
     * @return array
     */
    protected function returnFormatData(bool $status, string $code, string $msg, array $oriData): array
    {
        $returnObj = new CouponExtReturn();
        $returnObj->setStatus($status);
        $returnObj->setCode($code);
        $returnObj->setMsg($msg);
        $returnObj->setOriData($oriData);

        return $returnObj->getResultArray();
    }


    /**
     * 用户登录 code2Session
     * @param string $code
     * @return array
     * @throws GuzzleException
     */
    protected function codeToSession(string $code): array
    {
        if (config('app.env') == 'dev') {
            $url = self::SESSION_SAND_BOX_URL;
        } else {
            $url = self::SESSION_PROD_URL;
        }
        $params   = [
            'appid'          => $this->getClientKey(),
            'secret'         => $this->getClientSecret(),
            'code'           => $code,
            'anonymous_code' => '',
        ];
        $response = (new Client([
            'timeout' => self::TIME_OUT,
            'headers' => [
                'Content-Type' => "application/json",
            ],
        ]))->post($url, [RequestOptions::JSON => $params]);

        $result = json_decode($response->getBody()->getContents(), true);

        Log::info('DouyinBase code2Session:', [
            'params'   => $params,
            'url'      => $url,
            'contents' => $result,
        ]);

        return $result;
    }
}